package com.tnovoselec.android.ui.imagegallery;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.widget.ImageView;


public class TouchImageView extends ImageView {

    private Matrix matrix;

    private static final int NONE = 0;
    private static final int DRAG = 1;
    private static final int ZOOM = 2;
    private int mode = NONE;

    private PointF previousPoint = new PointF();
    private PointF mid = new PointF();
    private float oldDist = 1f;

    private boolean isOnHorizontalEdge;
    private boolean horizontalScrollEnabled;

    private float minScale = 1;
    private float maxScale = 1;

    private boolean firstLayout = true;

    private GestureDetector gestureDetector;

    public TouchImageView(Context context) {
	this(context, null);
    }

    public TouchImageView(Context context, AttributeSet attrs) {
	this(context, attrs, 0);
    }

    public TouchImageView(Context context, AttributeSet attrs, int defStyle) {
	super(context, attrs, defStyle);
	init();
    }

    private void init() {
	matrix = new Matrix();
	gestureDetector = new GestureDetector(getContext(), new GestureListener());

	isOnHorizontalEdge = false;
	horizontalScrollEnabled = true;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
	super.onLayout(changed, left, top, right, bottom);
	updateParams(firstLayout);
    }

    private void updateParams(boolean transform) {
	setScaleType(ScaleType.MATRIX);

	final Drawable drawable = getDrawable();
	if (drawable != null) {
	    final int w = drawable.getIntrinsicWidth();
	    final int h = drawable.getIntrinsicHeight();

	    if (w > 0 && h > 0) {
		final float viewRatio = (float) getWidth() / getHeight();
		final float drawableRatio = (float) w / h;
		if (viewRatio < drawableRatio) {
		    minScale = (float) getWidth() / w;
		} else {
		    minScale = (float) getHeight() / h;
		}
		maxScale = 4 * minScale;
		if (transform) {
		    if (minScale < 1.5) {
			matrix.setScale(minScale, minScale);
		    }
		    matrix.postTranslate(0, (getHeight() - drawable.getIntrinsicHeight() * minScale) / 2);
		    if (drawable instanceof BitmapDrawable) {
			firstLayout = false;
		    }
		}
	    }
	}

	fixBounds();

	setImageMatrix(matrix);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
	gestureDetector.onTouchEvent(event);

	final float[] values = new float[9];
	matrix.getValues(values);

	switch (event.getAction() & MotionEvent.ACTION_MASK) {
	    case MotionEvent.ACTION_DOWN:
		previousPoint.set(event.getX(), event.getY());
		mode = DRAG;
		break;
	    case MotionEvent.ACTION_MOVE:
		if (mode == DRAG) {
		    isOnHorizontalEdge = false;

		    final float x = values[Matrix.MTRANS_X];
		    final float imageWidthScaled = getDrawable().getIntrinsicWidth() * values[Matrix.MSCALE_X];
		    final float imageHeightScaled = getDrawable().getIntrinsicHeight() * values[Matrix.MSCALE_Y];

		    float dx = event.getX() - previousPoint.x;
		    float dy = event.getY() - previousPoint.y;

		    if (horizontalScrollEnabled == false) {
			dx = 0;
		    } else {
			if (imageWidthScaled > getWidth()) {
			    if (dx > 0) {
				dx = Math.min(dx, -x);
			    }
			    if (dx < 0) {
				dx = Math.max(dx, getWidth() - imageWidthScaled - x);
			    }
			} else {
			    dx = 0;
			}
		    }
		    if (imageHeightScaled <= getHeight()) {
			dy = 0;
		    }

		    if (dx == 0) {
			isOnHorizontalEdge = true;
		    }

		    matrix.postTranslate(dx, dy);
		} else if (mode == ZOOM) {
		    final float currentScale = values[Matrix.MSCALE_X];

		    float newDist = spacing(event);
		    if (newDist > 10f) {
			float scale = newDist / oldDist;
			if (scale > 1) {
			    scale = Math.min(scale * currentScale, maxScale) / currentScale;
			} else {
			    scale = Math.max(scale * currentScale, minScale) / currentScale;
			}
			matrix.postScale(scale, scale, mid.x, mid.y);
			oldDist = newDist;
		    }
		}
		previousPoint.set(event.getX(), event.getY());
		break;
	    case MotionEvent.ACTION_POINTER_DOWN:
		oldDist = spacing(event);
		if (oldDist > 10f) {
		    midPoint(mid, event);
		    mode = ZOOM;
		}
		break;
	    case MotionEvent.ACTION_UP:
		int xDiff = (int) Math.abs(event.getX() - previousPoint.x);
		int yDiff = (int) Math.abs(event.getY() - previousPoint.y);
		if (xDiff < 15 && yDiff < 15) {
		    performClick();
		}

		fixBounds();
	    case MotionEvent.ACTION_POINTER_UP:
		mode = NONE;
		break;
	}

	setImageMatrix(matrix);
	return true;
    }

    private void fixBounds() {
	final float[] values = new float[9];
	matrix.getValues(values);

	final float x = values[Matrix.MTRANS_X];
	final float y = values[Matrix.MTRANS_Y];
	final float scale = values[Matrix.MSCALE_X];
	final float imageWidthScaled = getDrawable().getIntrinsicWidth() * values[Matrix.MSCALE_X];
	final float imageHeightScaled = getDrawable().getIntrinsicHeight() * values[Matrix.MSCALE_Y];
	if (imageHeightScaled < getHeight()) {
	    matrix.setScale(scale, scale);
	    matrix.postTranslate(x, (getHeight() - imageHeightScaled) / 2);
	} else {
	    if (y > 0) {
		matrix.postTranslate(0, -y);
	    }
	    if (getHeight() - imageHeightScaled - y > 0) {
		matrix.postTranslate(0, getHeight() - imageHeightScaled - y);
	    }
	}
	if (imageWidthScaled > getWidth()) {
	    if (x > 0) {
		matrix.postTranslate(-x, 0);
	    }
	    if (getWidth() - imageWidthScaled - x > 0) {
		matrix.postTranslate(getWidth() - imageWidthScaled - x, 0);
	    }
	} else {
	    matrix.postTranslate(-x + (getWidth() - imageWidthScaled) / 2, 0);
	}
    }

    /**
     * 
     * @return boolean - true if image is fully panned left or right. Useful if
     *         used in ImageGallery
     */
    public boolean isOnHorizontalEdge() {
	return isOnHorizontalEdge;
    }

    /**
     * @param horizontalScrollEnabled
     *            If set to false, view ignores x component of MotionEvent.
     *            Useful if used in ImageGallery
     */
    public void setHorizontalScrollEnabled(boolean horizontalScrollEnabled) {
	this.horizontalScrollEnabled = horizontalScrollEnabled;
    }

    private float spacing(MotionEvent event) {
	final float x0 = MotionEventCompat.getX(event, 0);
	final float x1 = MotionEventCompat.getX(event, 1);
	final float y0 = MotionEventCompat.getY(event, 0);
	final float y1 = MotionEventCompat.getY(event, 1);
	final float x = x0 - x1;
	final float y = y0 - y1;
	return FloatMath.sqrt(x * x + y * y);
    }

    private void midPoint(PointF point, MotionEvent event) {
	final float x0 = event.getX(0);
	final float x1 = event.getX(1);
	final float y0 = event.getY(0);
	final float y1 = event.getY(1);
	final float x = (x0 + x1) / 2;
	final float y = (y0 + y1) / 2;
	point.set(x, y);
    }

    private class GestureListener extends SimpleOnGestureListener {

	@Override
	public boolean onDoubleTap(MotionEvent e) {
	    final float[] values = new float[9];
	    matrix.getValues(values);

	    final float scale = values[Matrix.MSCALE_X];

	    if (scale < minScale * 1.05) {
		matrix.postScale(2, 2, e.getX(), e.getY());
	    } else {
		matrix.postScale(minScale / scale, minScale / scale);
	    }

	    fixBounds();
	    setImageMatrix(matrix);
	    return true;
	}
    }

}
